package com.java.serverpackage;

import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
import java.io.IOException;

public class MultiThreadedServer implements Runnable{

    protected int          serverPort   = 8080;
    protected ServerSocket serverSocket = null;
    protected boolean      isStopped    = false;
    protected Thread       runningThread= null;
	protected Random       randomGenerator;
	
	private static int roomID = 0; 
	
	protected static ArrayList<ClientRoom> clientRoomList = new ArrayList<ClientRoom>();
    protected static HashMap<InetAddress, Boolean> connectedClients = new HashMap<InetAddress, Boolean>();
	
	private final static int MAX_NUMBER_CLIENTS = 2;
    
    public MultiThreadedServer(int port){
        this.serverPort = port;
        randomGenerator = new Random();        
    }

    public void run(){
        synchronized(this){
            this.runningThread = Thread.currentThread();
        }
        openServerSocket();
        while(! isStopped()){        	
        	
        	while(true){
	            Socket clientSocket = null;
	            try {
	            	
	            	clientSocket = this.serverSocket.accept();
	           
	            }catch (IOException e) {
	                if(isStopped()) {
	                    System.out.println("Server Stopped.") ;
	                    return;
	                }
	                throw new RuntimeException("Error accepting client connection", e);
	            }
	            
	            //Check if client is already connected, if he is, disconnect him, and call the great cthulhu on his ass.
	            
	            InetAddress clientIP = clientSocket.getInetAddress();
	            
	            if(connectedClients.get(clientIP) == null || connectedClients.get(clientIP) == false) {
	            	connectedClients.put(clientIP,true);
	            	
	                System.out.println("Client accepted" + clientSocket.getInetAddress().toString());
	                
	                // Create a new ClientRunnable with a new socket
		            ClientRunnable clientRunnable = new ClientRunnable(clientSocket);
		            
		            lookingForRoom(clientRunnable);
		            
		            new Thread(clientRunnable).start();
	            }
	            else if(connectedClients.get(clientIP) == true){
	            	try {
	            	    System.out.println("Client already connected" + clientSocket.getInetAddress().toString());
						connectedClients.put(clientIP, false);
	            	    clientSocket.close();
					}catch (IOException e) { }// e.printStackTrace(); 
	            }
        	}
        }
        System.out.println("Server Stopped.") ;
     }
   
    protected static synchronized void incrementRoomID(){
    	roomID++;
    }
    
    protected static synchronized void decrementRoomID(){
    	roomID--;
    }
    
    private synchronized void lookingForRoom(ClientRunnable clientRunnable){
        // Look for a room that has less than x ClientRunnables
        
        boolean foundRoom = false;
        int tries = 0;
        
        // First try to pick a random room and add the ClientRunnable
        // but only try 3 times
        while(!foundRoom && tries < 3 && !clientRoomList.isEmpty()) {
        	tries++;
        	int index = randomGenerator.nextInt(clientRoomList.size());
        	ClientRoom room = clientRoomList.get(index);
        	if(room.getMembers() < MAX_NUMBER_CLIENTS) {
        		foundRoom = true;
        		room.addClient(clientRunnable);
        		clientRunnable.setRoom(room);
        		break;
        	}
        }
       
        // If no room was found a random then check all the rooms
        // beginning from the start of the list
        if(!foundRoom && !clientRoomList.isEmpty()) {	  	
	        for(ClientRoom room : clientRoomList) {
	            if(room.getMembers() < MAX_NUMBER_CLIENTS) {
	            	foundRoom = true;
	            	room.addClient(clientRunnable);
	            	clientRunnable.setRoom(room);
	            	break;
	            }
	        }
        }

        // If all the existing ClientRooms are full
        // create a new ClientRoom and add clientRunnable
        if(!foundRoom) {
        	incrementRoomID();
        	ClientRoom clientRoom = new ClientRoom(roomID);
        	clientRoom.addClient(clientRunnable);
        	clientRoomList.add(clientRoom);
        	clientRunnable.setRoom(clientRoom);
        	//roomID++;
        }    		
    }
    
    
    private synchronized boolean isStopped() {
        return this.isStopped;
    }

    public synchronized void stop(){
        this.isStopped = true;
        try {
            this.serverSocket.close();
        } catch (IOException e) {
            throw new RuntimeException("Error closing server", e);
        }
    }

    private void openServerSocket() {
        try {
            this.serverSocket = new ServerSocket(this.serverPort);
        } catch (IOException e) {
            throw new RuntimeException("Cannot open port 5163", e);
        }
    }

}
